#pragma once

#if defined(WIN32) || defined(_WIN32)

#include "IOMultiplexer.hpp"

#include <winsock2.h>

namespace network {

    class Select : public IOMultiplexer {

    public:

        /**
          * @brief  创建一个select模型
          */
        Select();

        /**
          * @brief  设置select关注指定文件描述符的指定IO事件
          * @param  fd 要加入检测的文件描述符
          * @param  event_type 关注的IO事件
          * @retval 是否成功将事件添加到select模型上
          */
        bool add(int fd, EventType event) override;

        /**
          * @brief  修改select关注的事件类型
          * @param  fd    要修改的文件描述符
          * @param  event 要关注的类型
          * @retval 是否修改成功
          */
        bool mod(int fd, EventType event) override;

        /**
          * @brief  将select对象上指定的fd对应的master_read_set删除
          * @note   该函数在删除指定的fd后不会自动关闭该fd，请手动关闭
          * @param  要删除的fd
          * @retval 是否成功删除
          */
        bool del(int fd) override;

        /**
          * @brief  等待select文件描述符上的I/O事件
          * @note   select返回小于0时会抛出runtime_error异常
          * @param  reactor 反应堆指针
          * @retval 就绪事件个数
          */
        int dispatch(Reactor *reactor) override;

        ~Select() override = default;

    private:

        // 读描述符集合
        fd_set master_read_set_, read_set_;

        // 写描述符集合
        fd_set master_write_set_, write_set_;

        // 最大文件描述符
        int max_fd_;

        // 超时时长
        struct timeval timeout_;

    };

#include <stdexcept>

    Select::Select() {
        FD_ZERO(&master_read_set_);
        timeout_.tv_sec = 0;
        timeout_.tv_sec = 0;
        max_fd_ = 0;
    }

    bool Select::add(int fd, EventType event) {
        if (hasEvent(event, EventType::Read)) {
            FD_SET(fd, &master_read_set_);
        } else if (hasEvent(event, EventType::Write)) {
            FD_SET(fd, &master_write_set_);
        }
        max_fd_ = std::max(fd, max_fd_);
        return true;
    }

    bool Select::mod(int fd, EventType event) {
        if (hasEvent(event, EventType::Read)) {
            FD_CLR(fd, &master_write_set_);
            FD_SET(fd, &master_read_set_);
        } else if (hasEvent(event, EventType::Write)) {
            FD_CLR(fd, &master_read_set_);
            FD_SET(fd, &master_write_set_);
        }
        max_fd_ = std::max(fd, max_fd_);
        return true;
    }

    bool Select::del(int fd) {
        FD_CLR(fd, &master_read_set_);
        return false;
    }

    int Select::dispatch(Reactor *reactor) {
        read_set_ = master_read_set_;
        write_set_ = master_write_set_;
        int ret = select(max_fd_ + 1, &read_set_, &write_set_, nullptr, &timeout_);
        if (ret < 0) {
            throw std::runtime_error("select return < 0");
        }
        for (int fd = 0; fd <= max_fd_; ++fd) {
            if (FD_ISSET(fd, &read_set_)) {
                reactor->addEvent(fd, EventType::Read);
            } else if (FD_ISSET(fd, &write_set_)) {
                reactor->addEvent(fd, EventType::Write);
            }
        }
        return ret;
    }

}

#endif